﻿/*----------------------------------------------------------------
// Copyright (C) 2023 版权所有。 
//
// 文件功能描述：   Stacking控制类，由任务调度者直接使用
// 文件创建者：     王志国
// 时间：           2023 年 7 月 12 日
// 
// 修改履历：
// 时间         作者       修改内容                   修改原因
// 2023/7/12   王志国      新建
//----------------------------------------------------------------*/
using Goway.Log4net;
using Goway.StackingHelper.Inovance;
using Goway.StackingUC.Common;
using Goway.StackingUC.Common.Helper;
using Goway.StackingUC.Entities;
using Goway.StackingUC.Entities.BizData;
using Goway.StackingUC.Entities.DataDefinition;
using Goway.StackingUC.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Goway.Stacking.StackingControl
{
    /// <summary>
    /// Stacking 控制
    /// </summary>
    public class StackingControllerBak : Subject
    {
        /// <summary>
        /// 是否已连接
        /// </summary>
        public bool IsConnected { get; private set; }

        /// <summary>
        /// Stacking 放料位置是否发生故障
        /// </summary>
        public bool IsOccurFault { get; private set; }

        /// <summary>
        /// 当前堆垛机信息
        /// </summary>
        private DeviceInfo stackingInfo;

        /// <summary>
        /// 所属库区信息
        /// </summary>
        private WarehouseArea warehouseArea;

        /// <summary>
        /// 堆垛机 IP
        /// </summary>
        private string stackingIPAddr;

        /// <summary>
        /// 端口
        /// </summary>
        private int stackingPort;

        /// <summary>
        /// 当前任务处理结果
        /// </summary>
        private WorkErrorCode currentTaskResult;

        /// <summary>
        /// Stacking 通知消息
        /// </summary>
        private StackingNotify notifyObj;

        /// <summary>
        /// 当前任务编号
        /// </summary>
        public int CurrentTaskNo { get; private set; }

        /// <summary>
        /// 当前任务状态
        /// </summary>
        public StackingTaskStatus CurrentTaskStatus { get; private set; }

        /// <summary>
        /// Stacking 默认端口
        /// </summary>
        private const int Stacking_DEFAULT_PORT = 502;

        /// <summary>
        /// 多次访问Stacking时间间隔
        /// </summary>
        private const int WAIT_TIME_INTEVAL = 500;

        /// <summary>
        /// 等待 Stacking 空闲访问时间间隔
        /// </summary>
        private const int WAIT_FREE_TIME_INTEVAL = 100;

        /// <summary>
        /// 入库扫码消息响应超时时长
        /// </summary>
        private const int WRITE_SCAN_INFO_EXPIRED_SECONDS = 60;

        /// <summary>
        /// 做任务超时时长（入库、出库、移库）
        /// </summary>
        private const int DO_WORK_EXPIRED_SECONDS = 200;

        #region 物料坐标

        /// <summary>
        /// 取料坐标
        /// </summary>
        private ushort[,] fromCoordinate;

        /// <summary>
        /// 放料坐标
        /// </summary>
        private ushort[,] toCoordinate;

        /// <summary>
        /// 入库缓存坐标
        /// </summary>
        public ushort[,] InputBufferCoord { get; private set; }

        /// <summary>
        /// 出库缓存坐标
        /// </summary>
        public ushort[,] OutputBufferCoord { get; private set; }

        /// <summary>
        /// 放料位置故障
        /// </summary>
        public short[] MalfunctionToCoordinate { get; private set; }

        #region 出入库缓存坐标相关配置信息

        /// <summary>
        /// 入库缓存所在列号
        /// </summary>
        private ushort inputAtColumnIdx;

        /// <summary>
        /// 入库缓存起始层号
        /// </summary>
        private ushort inputBeginLayerIdx;

        /// <summary>
        /// 入库缓存终止层号
        /// </summary>
        private ushort inputEndLayerIdx;

        /// <summary>
        /// 出库缓存所在列号
        /// </summary>
        private ushort outputAtColumnIdx;

        /// <summary>
        /// 出库缓存起始层号
        /// </summary>
        private ushort outputBeginLayerIdx;

        /// <summary>
        /// 出库缓存终止层号
        /// </summary>
        private ushort outputEndLayerIdx;

        #endregion

        #endregion

        #region 常量定义

        /// <summary>
        /// 坐标数组行数
        /// </summary>
        public const int COORDINATE_ROW_COUNT = 8;

        /// <summary>
        /// 坐标数组列数
        /// </summary>
        public const int COORDINATE_COLUMN_COUNT = 3;

        /// <summary>
        /// 每包物料数/每次整取整放移动的数
        /// </summary>
        public ushort MaterialCountPeerPackage { get; private set; }

        /// <summary>
        /// 库排数
        /// </summary>
        private ushort storageRowCount;

        /// <summary>
        /// 库列数
        /// </summary>
        private ushort storeColumnCount;

        /// <summary>
        /// 库层数
        /// </summary>
        private ushort storageLayerCount;

        /// <summary>
        /// 库临时区域层数
        /// </summary>
        private ushort storageTempAreaLayerCount;

        #endregion

        /// <summary>
        /// 构造
        /// </summary>
        /// <param name="areaId">分库编号</param>
        public StackingControllerBak(List<Observer> observers)
        {
            this.observers = observers;
        }

        /// <summary>
        /// 初始化
        /// </summary>
        /// <param name="materialCountPeerPackage">每包物料数</param>
        /// <param name="tempAreaLayerCount">临时区域层数</param>
        /// <param name="inputAtColumnIdx">入库缓存所在列</param>
        /// <param name="inputBeginLayerIdx">入库缓存起始层号</param>
        /// <param name="inputEndLayerIdx">入库缓存终止层号</param>
        /// <param name="outputAtColumnIdx">出库缓存所在列</param>
        /// <param name="outputBeginLayerIdx">出库缓存起始层号</param>
        /// <param name="outputEndLayerIdx">出库缓存终止层号</param>
        /// <param name="errorMsg">错误消息</param>
        /// <remarks>所有坐标都从1开始，不能为 0</remarks>
        /// <returns>是否正常完成</returns>
        public bool Initialization(WarehouseArea area, ushort materialCountPeerPackage, ushort tempAreaLayerCount,
            ushort inputAtColumnIdx, ushort inputBeginLayerIdx, ushort inputEndLayerIdx, ushort outputAtColumnIdx,
            ushort outputBeginLayerIdx, ushort outputEndLayerIdx, out string errorMsg)
        {
            errorMsg = "";

            if (area == null || 0 == materialCountPeerPackage || 0 == tempAreaLayerCount ||
                0 == inputAtColumnIdx || 0 == inputBeginLayerIdx || 0 == inputEndLayerIdx ||
                0 == outputAtColumnIdx || 0 == outputBeginLayerIdx || 0 == outputEndLayerIdx)
            {
                errorMsg = "参数错误，初始化失败！";
                return false;
            }

            if (area == null)
            {
                errorMsg = "当前 Stacking 所在库区信息为空，初始化失败！";
                return false;
            }

            warehouseArea = area;

            // 初始化各参数
            this.storageRowCount = warehouseArea.RowCount.HasValue ? warehouseArea.RowCount.Value : (ushort)0;
            this.storeColumnCount = warehouseArea.ColumnCount.HasValue ? warehouseArea.ColumnCount.Value : (ushort)0;
            this.storageLayerCount = warehouseArea.LayerCount.HasValue ? warehouseArea.LayerCount.Value : (ushort)0;

            this.MaterialCountPeerPackage = materialCountPeerPackage;
            this.storageTempAreaLayerCount = tempAreaLayerCount;

            this.inputAtColumnIdx = inputAtColumnIdx;
            this.inputBeginLayerIdx = inputBeginLayerIdx;
            this.inputEndLayerIdx = inputEndLayerIdx;
            this.outputAtColumnIdx = outputAtColumnIdx;
            this.outputBeginLayerIdx = outputBeginLayerIdx;
            this.outputEndLayerIdx = outputEndLayerIdx;

            if (!InitCoordinate(out errorMsg))
            {
                errorMsg = $"Stacking出入库缓存坐标初始化错误：{errorMsg}，初始化失败！";
                return false;
            }

            DeviceInfoService serv = new DeviceInfoService();
            this.stackingInfo = serv.GetSingle(stacking => stacking.AreaId == this.warehouseArea.Id &&
                stacking.DeviceType == (int)DeviceType.Stacking);
            if (this.stackingInfo == null)
            {
                errorMsg = "未能在数据库查询到该库区的Stacking信息，初始化失败！";
                return false;
            }

            this.stackingIPAddr = this.stackingInfo.DeviceIP;
            this.stackingPort = this.stackingInfo.DevicePort == null ? 0 : this.stackingInfo.DevicePort.Value;

            return true; // this.Connect(out errorMsg);
        }

        /// <summary>
        /// 初始化坐标数据
        /// </summary>
        private bool InitCoordinate(out string errorMsg)
        {
            errorMsg = "";

            this.fromCoordinate = new ushort[COORDINATE_ROW_COUNT, COORDINATE_COLUMN_COUNT];
            this.toCoordinate = new ushort[COORDINATE_ROW_COUNT, COORDINATE_COLUMN_COUNT];

            this.InputBufferCoord = new ushort[COORDINATE_ROW_COUNT, COORDINATE_COLUMN_COUNT];
            this.OutputBufferCoord = new ushort[COORDINATE_ROW_COUNT, COORDINATE_COLUMN_COUNT];

            // 初始化为 0
            for (int i = 0; i < COORDINATE_ROW_COUNT; i++)
            {
                for (int j = 0; j < COORDINATE_COLUMN_COUNT; j++)
                {
                    this.fromCoordinate[i, j] = 0;
                    this.toCoordinate[i, j] = 0;

                    this.InputBufferCoord[i, j] = 0;
                    this.OutputBufferCoord[i, j] = 0;
                }
            }

            // 初始化出入库缓存地址
            if (!this.warehouseArea.InputBufAtRow.HasValue || !this.warehouseArea.OutputBufAtRow.HasValue ||
                this.inputAtColumnIdx == 0 || this.inputBeginLayerIdx == 0 || this.inputEndLayerIdx == 0 ||
                this.outputAtColumnIdx == 0 || this.outputBeginLayerIdx == 0 || this.outputEndLayerIdx == 0 ||
                this.inputEndLayerIdx < this.inputBeginLayerIdx || this.outputEndLayerIdx < this.outputBeginLayerIdx ||
                this.inputEndLayerIdx - this.inputBeginLayerIdx + 1 > this.MaterialCountPeerPackage ||
                this.outputEndLayerIdx - this.outputBeginLayerIdx + 1 > this.MaterialCountPeerPackage)
            {
                errorMsg = $"库区{this.warehouseArea.Id}出入库坐标的系统参数错误，或坐标数据非法。";
                return false;
            }

            for (int i = 0; i < this.inputEndLayerIdx - this.inputBeginLayerIdx + 1; i++)
            {
                this.InputBufferCoord[i, 0] = this.warehouseArea.InputBufAtRow.Value;
                this.InputBufferCoord[i, 1] = this.inputAtColumnIdx;
                this.InputBufferCoord[i, 2] = (ushort)(this.inputBeginLayerIdx + i);
            }

            for (int i = 0; i < this.outputEndLayerIdx - this.outputBeginLayerIdx + 1; i++)
            {
                this.OutputBufferCoord[i, 0] = this.warehouseArea.OutputBufAtRow.Value;
                this.OutputBufferCoord[i, 1] = this.outputAtColumnIdx;
                this.OutputBufferCoord[i, 2] = (ushort)(this.outputBeginLayerIdx + i);
            }

            return true;
        }

        #region 连接与断开

        /// <summary>
        /// 连接
        /// </summary>
        /// <param name="errorMsg"></param>
        /// <returns></returns>
        public bool Connect(out string errorMsg)
        {
            errorMsg = "";
            if (HC_AM600_ModbusTcp.Init_ETH_String(this.stackingIPAddr, 0,
                this.stackingPort == 0 ? Stacking_DEFAULT_PORT : this.stackingPort))
            {
                errorMsg = $"堆垛机已连接。";
                IsConnected = true;

                // 发送心跳
                // WriteHeartbeat();
            }
            else
            {
                errorMsg = $"堆垛机连接失败。";
                IsConnected = false;

                return false;
            }

            return true;
        }

        /// <summary>
        /// 断开连接
        /// </summary>
        /// <param name="errorMsg"></param>
        /// <returns></returns>
        public bool DisConnect(out string errorMsg)
        {
            errorMsg = "";
            if (HC_MODBUSTCPV2.Exit_ETH())
            {
                errorMsg = "堆垛机已断开连接";

                IsConnected = false;
                return true;
            }
            else
            {
                errorMsg = "堆垛机断开连接失败";
                return false;
            }
        }

        /// <summary>
        /// 检查是否已连接
        /// </summary>
        /// <param name="errorMsg"></param>
        /// <returns></returns>
        private bool CheckIsConnect(out string errorMsg)
        {
            errorMsg = "";
            if (!IsConnected)
            {
                if (!this.Connect(out errorMsg))
                {
                    errorMsg = "连接失败";
                    return false;
                }
            }

            return true;
        }

        #endregion

        #region 业务流程控制

        #region 已删除
        /*
        /// <summary>
        /// W_写入心跳和运行状态
        /// </summary>
        /// <returns></returns>
        public async void WriteHeartbeat()
        {
            if (!CheckIsConnect(out string errorMsg))
            {
                LoggerHelper.Error($"{this.stackingIPAddr} 连接失败");
                return;
            }

            var task = SendHeartbeatTask();
            await task;
        }

        /// <summary>
        /// 发送心跳的异步处理
        /// </summary>
        /// <returns></returns>
        public async Task<bool> SendHeartbeatTask()
        {
            string errorMsg = "";
            short writeValue = StackingRWContent.RW_CONTENT_1;

            var task = Task<bool>.Run(() =>
            {
                while (IsConnected)
                {
                    try
                    {
                        Thread.Sleep(WAIT_TIME_INTEVAL);

                        // 写心跳
                        int writeResult = WriteStackingInt16Data(ProtocolAddress.R_RECEIVED_TASK_FLAG, 1,
                            new short[] { writeValue }, out errorMsg);
                        if (writeResult != StackingErrorCode.SUCCESS)
                        {
                            LoggerHelper.Info($"写心跳失败：{errorMsg}");
                        }

                        // 修改心跳值
                        if (writeValue == StackingRWContent.RW_CONTENT_1)
                        {
                            writeValue = StackingRWContent.RW_CONTENT_0;
                        }
                        else
                        {
                            writeValue = StackingRWContent.RW_CONTENT_1;
                        }
                    }
                    catch (Exception ex)
                    {
                        LoggerHelper.Error($"写心跳异常：{ex}");

                        // 重连
                        if (!DisConnect(out errorMsg))
                        {
                            LoggerHelper.Error($"{this.stackingIPAddr}断开连接失败，{errorMsg}");
                            return false;
                        }

                        IsConnected = false;
                        if (!Connect(out errorMsg))
                        {
                            LoggerHelper.Error($"{this.stackingIPAddr}连接失败，{errorMsg}");
                            return false;
                        }

                        return true;                           // 重连后会重新启动异步方法，该处理退出
                    }
                }

                return true;
            });

            var result = await task;
            return result;
        }
        */
        #endregion

        /// <summary>
        /// 发送心跳线程方法
        /// </summary>
        /// <param name="token"></param>
        public void SendHeartbeat(object token)
        {
            if (token is CancellationToken)
            {
                var tok_source = (CancellationToken)token;
                short writeValue = RWContent.RW_VALUE_1;
                while (!tok_source.IsCancellationRequested)
                {
                    try
                    {
                        Thread.Sleep(WAIT_TIME_INTEVAL);

                        // 写心跳
                        int writeResult = WriteStackingInt16Data(ProtocolAddress.R_RECEIVED_TASK_FLAG, 1,
                            new short[] { writeValue }, out string errorMsg);
                        if (writeResult != RWErrorCode.SUCCESS)
                        {
                            LoggerHelper.Info($"写心跳失败：{errorMsg}");
                        }

                        // 修改心跳值
                        if (writeValue == RWContent.RW_VALUE_1)
                        {
                            writeValue = RWContent.RW_VALUE_0;
                        }
                        else
                        {
                            writeValue = RWContent.RW_VALUE_1;
                        }
                    }
                    catch (Exception ex)
                    {
                        LoggerHelper.Error($"写心跳异常：{ex}");

                        // 重连
                        if (!DisConnect(out string errorMsg))
                        {
                            LoggerHelper.Error($"{this.stackingIPAddr}断开连接失败，{errorMsg}");
                        }

                        IsConnected = false;
                        if (!Connect(out errorMsg))
                        {
                            LoggerHelper.Error($"{this.stackingIPAddr}连接失败，{errorMsg}");
                        }
                    }
                }
            }
        }

        /// <summary>
        /// W_写入任务下发标志
        /// </summary>
        /// <param name="bizType">业务类型，该字段用于业务判断，不下发给 Stacking</param>
        /// <param name="errorMsg">返回的错误消息</param>
        /// <returns></returns>
        public WorkErrorCode SendTaskOrder(BizType bizType, int currentTaskNo, out string errorMsg)
        {
            errorMsg = "";
            IsOccurFault = false;
            currentTaskResult = WorkErrorCode.UnKnown;
            this.CurrentTaskNo = currentTaskNo;

            if (!CheckIsConnect(out errorMsg))
            {
                CurrentTaskStatus = StackingTaskStatus.Connecting;
                currentTaskResult = WorkErrorCode.ConnectFailed;
                return currentTaskResult;
            }

            // 处理业务
            StackingAsyncDoWork(DO_WORK_EXPIRED_SECONDS);                      // 超时时长 200 s

            this.notifyObj = new StackingNotify()
            {
                CurrentTaskNo = this.CurrentTaskNo,
                CurrentTaskStatus = this.CurrentTaskStatus,
                ErrorCode = currentTaskResult,
                
                NotifyType = StackingNotifyType.InputTaskFinished
            };

            currentTaskResult = WorkErrorCode.Success;         // 启动任务成功，任务成功后，会有通知消息
            return currentTaskResult;
        }

        /// <summary>
        /// 异步执行 Stacking 任务
        /// </summary>
        /// <param name="expiredSeconds">超时时长</param>
        public async void StackingAsyncDoWork(int expiredSeconds)
        {
            var task = DoWork(expiredSeconds);
            await task;
        }

        /// <summary>
        /// 等待任务完成异步处理
        /// </summary>
        /// <param name="expiredSeconds">超时时长 单位：s</param>
        /// <returns>是否正常完成任务</returns>
        public async Task<WorkErrorCode> DoWork(int expiredSeconds)
        {
            string errorMsg = "";
            TimeSpan runTime = TimeSpan.Zero;

            var task = Task<bool>.Run(() =>
            {
                System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
                sw.Start();

                do
                {
                    // 等待堆垛机空闲状态
                    bool isFree = false;
                    do
                    {
                        // 判断是否超时
                        //runTime = sw.Elapsed;
                        //// 判断是否超时
                        //if (runTime.TotalSeconds > expiredSeconds)
                        //{
                        //    sw.Stop();
                        //    runTime = sw.Elapsed;
                        //    LoggerHelper.Error($"任务已超时！用时 {runTime.TotalMilliseconds}ms");

                        //    currentTaskResult = StackingWorkErrorCode.Expire;
                        //    return currentTaskResult;
                        //}

                        // 任务状态
                        CurrentTaskStatus = StackingTaskStatus.WaitStackingFree;

                        // 判断是否是自动模式并且空闲
                        isFree = true; // this.IsAutoRunMode && this.IsNotBusy;     // Test
                        if (!isFree)
                        {
                            Thread.Sleep(WAIT_FREE_TIME_INTEVAL);
                        }

                    } while (!isFree);

                    // 写取料坐标
                    if (WriteCoordinate(CoordinateType.From, out errorMsg) != WorkErrorCode.Success)
                    {
                        LoggerHelper.Error($"堆垛机{this.stackingIPAddr}写取料坐标失败：{errorMsg}");
                        return WorkErrorCode.WriteFailed;
                    }

                    // 写放料坐标
                    if (WriteCoordinate(CoordinateType.To, out errorMsg) != WorkErrorCode.Success)
                    {
                        LoggerHelper.Error($"堆垛机{this.stackingIPAddr}写放料坐标失败：{errorMsg}");
                        return WorkErrorCode.WriteFailed;
                    }

                    // 写指令
                    int rwResult = HC_AM600_ModbusTcp.WriteInt16(ProtocolAddress.W_ORDER, 1,
                        new short[] { RWContent.RW_VALUE_1 });
                    if (rwResult == RWErrorCode.FAILED)
                    {
                        errorMsg = $"堆垛机{this.stackingIPAddr}下发执行任务指令失败";
                        LoggerHelper.Error(errorMsg);
                        return WorkErrorCode.WriteFailed;
                    }

                    // 任务状态
                    CurrentTaskStatus = StackingTaskStatus.SentOrder;

                    int checkExpiredSeconds = 30;                            // 超时时间为20s
                    this.CheckRecvTaskOrder(checkExpiredSeconds);            // 读取收到指令位和指令位
                    if (IsOccurFault)
                    {
                        // continue;
                        errorMsg = "放料地址故障！";
                        LoggerHelper.Error(errorMsg);
                        return WorkErrorCode.BizFailed;
                    }

                    currentTaskResult = WorkErrorCode.UnKnown;            // 初始化
                    expiredSeconds = 100;                                    // 超时时间为100s
                    break;
                } while (IsOccurFault);

                // 等待执行结果
                while (true)
                {
                    runTime = sw.Elapsed;
                    // 判断是否超时
                    if (runTime.TotalSeconds > expiredSeconds)
                    {
                        sw.Stop();
                        runTime = sw.Elapsed;
                        LoggerHelper.Error($"任务已超时！用时 {runTime.TotalMilliseconds}ms");

                        currentTaskResult = WorkErrorCode.Expire;
                        return currentTaskResult;
                    }

                    Thread.Sleep(WAIT_TIME_INTEVAL);

                    // 读任务完成标记
                    short[] readResult = ReadStackingInt16Data(ProtocolAddress.R_TASK_FINISHED_FLAG, 1, out errorMsg);
                    if (readResult != null && readResult.Length > 0 && readResult[0] == RWContent.RW_VALUE_1)
                    {
                        // 任务状态
                        CurrentTaskStatus = StackingTaskStatus.RecvOrder;
                        break;
                    }
                }

                // 收到任务完成标记置 1
                if (RWErrorCode.SUCCESS != WriteStackingInt16Data(
                    ProtocolAddress.W_RECV_TASK_FINISHED_FLAG, 1,
                    new short[] { RWContent.RW_VALUE_1 }, out errorMsg))
                {
                    LoggerHelper.Error("堆垛机写入收到任务完成标记失败！");
                    currentTaskResult = WorkErrorCode.WriteFailed;
                    return currentTaskResult;
                }

                while (true)
                {
                    Thread.Sleep(WAIT_TIME_INTEVAL);

                    // 读任务完成标记
                    short[] readResult = ReadStackingInt16Data(ProtocolAddress.R_TASK_FINISHED_FLAG, 1, out errorMsg);
                    if (readResult != null && readResult.Length > 0 && readResult[0] == RWContent.RW_VALUE_0)
                    {
                        // 堆垛机收到完成任务标记置 0
                        if (RWErrorCode.SUCCESS != WriteStackingInt16Data(ProtocolAddress.W_RECV_TASK_FINISHED_FLAG, 1,
                            new short[] { RWContent.RW_VALUE_0 }, out errorMsg))
                        {
                            LoggerHelper.Error($"堆垛机完成标记置零失败！{errorMsg}");
                            currentTaskResult = WorkErrorCode.WriteFailed;
                            return currentTaskResult;
                        }

                        // 任务状态
                        CurrentTaskStatus = StackingTaskStatus.Finished;
                        break;
                    }
                }

                sw.Stop();
                runTime = sw.Elapsed;
                errorMsg = $"堆垛机{ this.stackingIPAddr} 任务完成，耗时：{ runTime.TotalSeconds}";
                LoggerHelper.Debug(errorMsg);

                // 通知观察者
                this.notifyObj.ErrorCode = this.currentTaskResult;
                this.notifyObj.ErrorMessage = errorMsg;
                this.notifyObj.CurrentTaskStatus = this.CurrentTaskStatus;                
                this.notifyObserver();

                currentTaskResult = WorkErrorCode.Success;
                return currentTaskResult;
            });

            var result = await task;
            return result;
        }

        /// <summary>
        /// 完成入库物料写入任务
        /// </summary>
        /// <param name="expiredSeconds"></param>
        /// <param name="inputCoord">入库物料起始和终止位置坐标</param>
        public async void DoWriteScanInfoTask(short[] inputCoord)
        {
            var task = WriteScanInfoTask(WRITE_SCAN_INFO_EXPIRED_SECONDS, inputCoord);
            await task;
        }

        /// <summary>
        /// 发送入库信息异步方法
        /// </summary>
        /// <param name="expiredSeconds">超时时长</param>
        /// <param name="inputCoord">入库信息【（起点/终点），（面/列/层）】</param>
        /// <returns>任务对象</returns>
        public async Task<WorkErrorCode> WriteScanInfoTask(int expiredSeconds, short[] inputCoord)
        {
            this.currentTaskResult = WorkErrorCode.UnKnown;

            if (expiredSeconds <= 0 || inputCoord == null || inputCoord.Length != 6)
            {
                LoggerHelper.Error("参数错误");
                this.currentTaskResult = WorkErrorCode.BizFailed;
                return currentTaskResult;
            }

            // 异步任务
            var task = Task<bool>.Run(() =>
            {
                System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
                TimeSpan runTime = TimeSpan.Zero;

                // 判断是否是自动模式并且空闲
                bool isFree = true; // this.IsAutoRunMode && this.IsNotBusy;     // Test
                if (!isFree)
                {
                    Thread.Sleep(WAIT_FREE_TIME_INTEVAL);
                }

                // 开启计时
                sw.Start();

                // 写入库信息 1300
                if (RWErrorCode.SUCCESS != WriteStackingInt16Data(ProtocolAddress.W_RECV_TASK_FINISHED_FLAG, 6,
                        inputCoord, out string errorMsg))
                {
                    LoggerHelper.Error($"入库信息写入失败！{errorMsg}");
                    currentTaskResult = WorkErrorCode.WriteFailed;
                    return currentTaskResult;
                }

                // 扫码完成标志置1   1002
                if (RWErrorCode.SUCCESS != WriteStackingInt16Data(
                        ProtocolAddress.W_SCAN_FINISHED, 1,
                        new short[] { RWContent.RW_VALUE_1 }, out errorMsg))
                {
                    LoggerHelper.Error($"堆垛机写入扫码完成标志失败！{errorMsg}");
                    currentTaskResult = WorkErrorCode.WriteFailed;
                    return currentTaskResult;
                }

                while (true)
                {
                    Thread.Sleep(WAIT_TIME_INTEVAL);

                    // 读Stacking收到扫码完成标志  1504
                    short[] readResult = ReadStackingInt16Data(ProtocolAddress.R_RECV_SCAN_FINISHED_FLAG, 1, out errorMsg);
                    if (readResult != null && readResult.Length > 0 && readResult[0] == RWContent.RW_VALUE_1)
                    {
                        break;
                    }
                    else
                    {
                        runTime = sw.Elapsed;
                        if (runTime.TotalSeconds > expiredSeconds)
                        {
                            currentTaskResult = WorkErrorCode.Expire;
                            return currentTaskResult;
                        }

                        // 没得到正确结果，继续等待
                    }
                }

                while (true)
                {
                    // 读Stacking收到扫码完成标志  1504
                    short[] readResult = ReadStackingInt16Data(ProtocolAddress.R_RECV_SCAN_FINISHED_FLAG, 1, out errorMsg);
                    if (readResult != null && readResult.Length > 0 && readResult[0] == RWContent.RW_VALUE_0)
                    {
                        // MES扫码完成标志复位
                        if (RWErrorCode.SUCCESS != WriteStackingInt16Data(ProtocolAddress.W_SCAN_FINISHED, 1,
                                new short[] { RWContent.RW_VALUE_0 }, out errorMsg))
                        {
                            LoggerHelper.Error($"入库信息写入失败！{errorMsg}");
                            currentTaskResult = WorkErrorCode.WriteFailed;
                            return currentTaskResult;
                        }

                        break;
                    }
                }

                sw.Stop();
                runTime = sw.Elapsed;
                LoggerHelper.Info($"完成一次入库物料信息写入，用时{runTime.TotalMilliseconds}ms。");
                this.currentTaskResult = WorkErrorCode.Success;
                return this.currentTaskResult;
            });

            var result = await task;
            return result;
        }

        /// <summary>
        /// 等待指令接收完成
        /// </summary>
        /// <param name="expiredSeconds"></param>
        public WorkErrorCode CheckRecvTaskOrder(int expiredSeconds)
        {
            bool isFinished = false;
            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
            string errorMsg = "";

            TimeSpan runTime = TimeSpan.Zero;
            sw.Start();

            while (!isFinished)
            {
                runTime = sw.Elapsed;
                // 判断是否超时
                if (runTime.TotalSeconds > expiredSeconds)
                {
                    sw.Stop();
                    LoggerHelper.Error("任务已超时！");
                    break;
                }

                Thread.Sleep(WAIT_TIME_INTEVAL / 10);

                // 等待 Stacking 设置“接收到任务标记”为 1
                short[] readResult = ReadStackingInt16Data(ProtocolAddress.R_RECEIVED_TASK_FLAG, 1, out errorMsg);
                if (!(readResult != null && readResult.Length > 0 && readResult[0] == RWContent.RW_VALUE_1))
                {
                    continue;
                }

                // 指令下发位，置零
                int writeResult = WriteStackingInt16Data(ProtocolAddress.W_ORDER, 1,
                    new short[] { RWContent.RW_VALUE_0 }, out errorMsg);
                if (writeResult != RWErrorCode.SUCCESS)
                {
                    LoggerHelper.Info($"写读取接收到指令位错误：{errorMsg}");
                    this.currentTaskResult = WorkErrorCode.WriteFailed;
                    return currentTaskResult;
                }

                // 判断放料位置是否故障
                this.MalfunctionToCoordinate = ReadStackingInt16Data(ProtocolAddress.R_TO_PLACE_MALFUNCTION, 8, out errorMsg);
                if (readResult != null && readResult.Length > 0)
                {
                    LoggerHelper.Info($"读取放料位置是否故障正确：{errorMsg}");
                    foreach (short resultValue in readResult)
                    {
                        if (resultValue == RWContent.OCCUR_MALFUNCTION_2)                   // 故障
                        {
                            IsOccurFault = true;
                            break;
                        }
                    }
                }
                else
                {
                    // 返回异常
                    currentTaskResult = WorkErrorCode.RWException;
                    return currentTaskResult;
                }

                sw.Stop();
                runTime = sw.Elapsed;
                LoggerHelper.Info($"任务已完成！耗时：{runTime.TotalMilliseconds}ms");

                isFinished = true;

                // 判断是否发生异常
                if (IsOccurFault)
                {
                    currentTaskResult = WorkErrorCode.BizFailed;
                    return currentTaskResult;                         // 返回错误
                }

                // 正常完成
                currentTaskResult = WorkErrorCode.Success;
                return currentTaskResult;
            }

            sw.Stop();
            runTime = sw.Elapsed;

            errorMsg = $"任务已超时！耗时：{runTime.TotalMilliseconds}ms";
            LoggerHelper.Info(errorMsg);

            currentTaskResult = WorkErrorCode.Expire;
            return currentTaskResult;

            //var task = CheckRecvTaskOrderFinished(expiredSeconds);
            //await task;
        }

        #region 已删除
        /*
        /// <summary>
        /// 等待接收到任务完成异步方法
        /// </summary>
        /// <param name="expiredSeconds">超时时长 单位：s</param>
        /// <returns>是否正常完成任务</returns>
        public async Task<bool> CheckRecvTaskOrderFinished(int expiredSeconds)
        {
            string errorMsg = "";
            TimeSpan runTime = TimeSpan.Zero;
            if (!CheckIsConnect(out errorMsg))
            {
                LoggerHelper.Error(errorMsg);
                return false;
            }

            bool isFinished = false;

            // 异步任务
            var task = Task<bool>.Run(() =>
            {
                System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
                sw.Start();

                while (!isFinished)
                {
                    runTime = sw.Elapsed;
                    // 判断是否超时
                    if (runTime.TotalSeconds > expiredSeconds)
                    {
                        sw.Stop();
                        LoggerHelper.Error("任务已超时！");
                        break;
                    }

                    Thread.Sleep(WAIT_TIME_INTEVAL / 10);

                    // 等待 Stacking 设置“接收到任务标记”为 1
                    short[] readResult = ReadStackingInt16Data(ProtocolAddress.R_RECEIVED_TASK_FLAG, 1, out errorMsg);
                    if (readResult != null && readResult.Length > 0 && readResult[0] == StackingRWContent.RW_VALUE_1)
                    {
                        LoggerHelper.Debug($"读取接收到指令位正确：{errorMsg}");
                    }
                    else
                    {
                        continue;
                    }

                    // 指令下发位，Stacking 置零，读取该位
                    readResult = ReadStackingInt16Data(ProtocolAddress.W_ORDER, 1, out errorMsg);
                    if (readResult != null && readResult.Length > 0 && readResult[0] == StackingRWContent.RW_VALUE_0)
                    {
                        LoggerHelper.Debug($"读取指令位置零正确：{errorMsg}");
                    }
                    else
                    {
                        continue;
                    }

                    // 设置“接收到任务标记”为 0
                    int writeResult = WriteStackingInt16Data(ProtocolAddress.R_RECEIVED_TASK_FLAG, 1,
                        new short[] { StackingRWContent.RW_VALUE_0 }, out errorMsg);
                    if (writeResult != StackingErrorCode.SUCCESS)
                    {
                        LoggerHelper.Info($"写读取接收到指令位错误：{errorMsg}");
                        return false;
                    }

                    // 判断放料位置是否故障
                    readResult = ReadStackingInt16Data(ProtocolAddress.R_TO_PLACE_MALFUNCTION, 8, out errorMsg);
                    if (readResult != null && readResult.Length > 0)
                    {
                        LoggerHelper.Info($"读取放料位置是否故障正确：{errorMsg}");
                        foreach (short resultValue in readResult)
                        {
                            if (resultValue == StackingRWContent.OCCUR_MALFUNCTION_2)                   // 故障
                            {
                                IsOccurFault = true;
                                break;
                            }
                        }
                    }
                    else
                    {
                        continue;
                    }

                    sw.Stop();
                    runTime = sw.Elapsed;
                    LoggerHelper.Info($"任务已完成！耗时：{runTime.TotalMilliseconds}ms");
                    isFinished = true;

                    if (IsOccurFault)
                    {
                        return false;                         // 返回错误
                    }
                }

                sw.Stop();
                runTime = sw.Elapsed;

                errorMsg = $"任务已超时！耗时：{runTime.TotalMilliseconds}ms";
                LoggerHelper.Info(errorMsg);
                return false;
            });

            var result = await task;
            return result;
        }
        */
        #endregion

        /// <summary>
        /// 写坐标
        /// </summary>
        /// <param name="coordType">坐标类型 0：取料坐标 1：放料坐标</param>
        /// <param name="msg">返回的错误消息</param>
        /// <returns>是否正常完成</returns>
        public WorkErrorCode WriteCoordinate(CoordinateType coordType, out string msg)
        {
            if (!CheckIsConnect(out msg))
            {
                return WorkErrorCode.ConnectFailed;
            }

            int index = 0;
            short[] dataArray = new short[COORDINATE_ROW_COUNT * COORDINATE_COLUMN_COUNT];

            try
            {
                if (coordType == CoordinateType.From)
                {
                    for (int i = 0; i < COORDINATE_ROW_COUNT; i++)
                    {
                        for (int j = 0; j < COORDINATE_COLUMN_COUNT; j++)
                        {
                            dataArray[index++] = (short)fromCoordinate[i, j];
                        }
                    }

                    // 写取料地址
                    int result = HC_AM600_ModbusTcp.WriteInt16(ProtocolAddress.W_FROM_COORDINATE,
                        dataArray.Length, dataArray);
                    if (result == 0)
                    {
                        msg = "取料地址写入Stacking失败";
                        return WorkErrorCode.WriteFailed;
                    }
                }
                else
                {
                    for (int i = 0; i < COORDINATE_ROW_COUNT; i++)
                    {
                        for (int j = 0; j < COORDINATE_COLUMN_COUNT; j++)
                        {
                            dataArray[index++] = (short)toCoordinate[i, j];
                        }
                    }

                    // 写放料地址
                    int result = HC_AM600_ModbusTcp.WriteInt16(ProtocolAddress.W_TO_COORDINATE,
                        dataArray.Length, dataArray);
                    if (result == 0)
                    {
                        msg = "放料地址写入Stacking失败";
                        return WorkErrorCode.WriteFailed;
                    }
                }
            }
            catch (Exception ex)
            {
                LoggerHelper.Fatal($"写Stacking地址操作，设置地址数据异常，{ex.Message}");
                return WorkErrorCode.RWException;
            }

            return WorkErrorCode.Success;
        }

        #endregion

        #region 读写 Stacking 基本方法

        /// <summary>
        /// 读取 Stacking 信息
        /// </summary>
        /// <param name="address">读取地址</param>
        /// <param name="length">读取内容长度</param>
        /// <param name="errorMsg"></param>
        /// <returns></returns>
        public short[] ReadStackingInt16Data(int address, int length, out string errorMsg)
        {
            short[] result = null;
            errorMsg = "";

            if (!CheckIsConnect(out errorMsg))
            {
                LoggerHelper.Error($"读Stacking入库缓存，Stacking 连接异常");
                return null;
            }

            result = HC_AM600_ModbusTcp.ReadInt16(address, length);
            return result;
        }

        /// <summary>
        /// 写Stacking Int16
        /// </summary>
        /// <param name="index"></param>
        /// <param name="writeLenth"></param>
        /// <param name="pValue"></param>
        /// <returns></returns>
        public int WriteStackingInt16Data(int index, int writeLenth, short[] pValue, out string errorMsg)
        {
            errorMsg = "";

            // 判断参数
            if (index <= 0 || writeLenth <= 0 || pValue == null || pValue.Length == 0 ||
                pValue.Length < writeLenth)
            {
                errorMsg = "参数错误";
                LoggerHelper.Error(errorMsg);
                return 0;
            }

            // 判断 Stacking 是否已连接
            if (!CheckIsConnect(out errorMsg))
            {
                LoggerHelper.Error($"读Stacking入库缓存，Stacking 连接异常");
                return 0;
            }

            return HC_AM600_ModbusTcp.WriteInt16(index, writeLenth, pValue);
        }


        #endregion

        /// <summary>
        /// 执行任务
        /// </summary>
        /// <returns>是否正常完成</returns>
        public WorkErrorCode RunInputTask(out string msg)
        {
            // 判断是否连接
            if (!CheckIsConnect(out msg))
            {
                return WorkErrorCode.ConnectFailed;
            }

            // 判断是否为自动模式


            return WorkErrorCode.Success;
        }

        #region 基本状态

        /// <summary>
        /// 是否为自动模式状态
        /// </summary>
        /// <returns></returns>
        public bool IsAutoRunMode
        {
            get
            {
                short[] readResult = ReadStackingInt16Data(ProtocolAddress.R_RUN_STATUS, 1, out string errorMsg);
                if (readResult != null && readResult.Length > 0)
                {
                    return readResult[0] == RWContent.RW_VALUE_1;
                }
                else
                {
                    LoggerHelper.Debug($"读取是否自动运行模式错误：{errorMsg}");
                    return false;
                }
            }
        }

        /// <summary>
        /// 是否为非忙碌状态
        /// </summary>
        /// <returns></returns>
        public bool IsFree
        {
            get
            {
                short[] readResult = ReadStackingInt16Data(ProtocolAddress.R_BUSY_STATUS, 1, out string errorMsg);
                if (readResult != null && readResult.Length > 0)
                {
                    return readResult[0] == RWContent.RW_VALUE_0;
                }
                else
                {
                    LoggerHelper.Debug($"读取是否忙碌状态错误：{errorMsg}");
                    return false;
                }
            }
        }

        /// <summary>
        /// 是否Stacking请求扫码（W_Stacking请求扫码）
        /// </summary>
        /// <returns></returns>
        public bool IsScanMaterial
        {
            get
            {
                short[] readResult = ReadStackingInt16Data(ProtocolAddress.R_Stacking_REQUEST_SCAN_BARCODE, 1, out string errorMsg);
                if (readResult != null && readResult.Length > 0 && readResult[0] == RWContent.RW_VALUE_1)
                {
                    return true;
                }

                LoggerHelper.Error($"读取Stacking{this.stackingIPAddr}请求扫码位错误：{errorMsg}");
                return false;
            }
        }

        /// <summary>
        /// 获得实际入库缓存的物料数量
        /// </summary>
        /// <returns></returns>
        public int InputFactCount
        {
            get
            {
                short[] readResult = ReadStackingInt16Data(ProtocolAddress.R_INPUT_BUFFER_COUNT, 1, out string errorMsg);
                if (readResult != null && readResult.Length > 0)
                {
                    return readResult[0];
                }
                else
                {
                    LoggerHelper.Debug($"读取入库缓存数量失败：{errorMsg}");
                    return 0;
                }
            }
        }

        #endregion

        /// <summary>
        /// 设置取/放料地址数组
        /// </summary>
        /// <param name="bizType">业务类型 可根据此字段自动添加 入库缓存 或出库缓存地址</param>
        /// <param name="srcCoordinate">源数据</param>
        /// <param name="coordType">坐标类型，0：取料地址 1：放料地址</param>
        /// <param name="errorMsg">错误消息</param>
        /// <returns></returns>
        public bool SetCoordinate(BizType bizType, CoordinateType coordType,
            ushort[,] srcCoordinate, out string errorMsg)
        {
            errorMsg = "";

            try
            {
                if (!(bizType == BizType.InputStorage && coordType == CoordinateType.From) &&
                    !(bizType == BizType.OutputStorage && coordType == CoordinateType.To) &&
                    (srcCoordinate == null ||
                    srcCoordinate.Length != COORDINATE_COLUMN_COUNT * COORDINATE_ROW_COUNT))
                {
                    errorMsg = $"设置取/放料地址参数错误！";
                    LoggerHelper.Fatal(errorMsg);
                    return false;
                }

                // 入库缓存
                if (bizType == BizType.InputStorage && coordType == CoordinateType.From)
                {
                    this.fromCoordinate = this.InputBufferCoord;
                    return true;
                }

                // 出库缓存
                if (bizType == BizType.OutputStorage && coordType == CoordinateType.To)
                {
                    this.toCoordinate = this.OutputBufferCoord;
                    return true;
                }

                for (int i = 0; i < MaterialCountPeerPackage; i++)          // 只使用有效数据 
                {
                    if (srcCoordinate[i, 0] == 0 /*|| srcCoordinate[i, 0] > storageRowCount*/)    // 排验证
                    {
                        errorMsg = $"设置取料地址参数排坐标错误：{srcCoordinate[i, 0]}";
                        LoggerHelper.Fatal(errorMsg);
                        return false;
                    }
                    if (srcCoordinate[i, 1] == 0 /*|| srcCoordinate[i, 1] > storeColumnCount*/)   // 列验证
                    {
                        errorMsg = $"设置取料地址参数列坐标错误：{srcCoordinate[i, 1]}";
                        LoggerHelper.Fatal(errorMsg);
                        return false;
                    }
                    if (srcCoordinate[i, 2] <= 0 || srcCoordinate[i, 2] > storageLayerCount)     // 层验证
                    {
                        errorMsg = $"设置取料地址参数层坐标错误：{srcCoordinate[i, 2]}";
                        LoggerHelper.Fatal(errorMsg);
                        return false;
                    }

                    for (int j = 0; j < COORDINATE_COLUMN_COUNT; j++)
                    {
                        if (coordType == 0)
                        {
                            this.fromCoordinate[i, j] = srcCoordinate[i, j];
                        }
                        else
                        {
                            this.toCoordinate[i, j] = srcCoordinate[i, j];
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                errorMsg = $"设置取料地址异常！{ex.Message}";
                LoggerHelper.Fatal(errorMsg);
                return false;
            }

            return true;
        }

        #region 工具方法

        /// <summary>
        /// 判断坐标是否为同一列上连续的库位
        /// </summary>
        /// <param name="currentCoordinate"></param>
        /// <returns></returns>
        private bool CheckCoordIsContinuous(ushort[,] currentCoordinate)
        {
            if (currentCoordinate == null || currentCoordinate.Length == 0 ||
                currentCoordinate.Length % 3 != 0)
            {
                return false;
            }

            ushort rowIndex = currentCoordinate[0, 0];
            ushort columnIndex = currentCoordinate[0, 1];
            ushort levelIndex = currentCoordinate[0, 2];
            for (int i = 0; i < currentCoordinate.Length / 3; i++)
            {
                // 排相等
                if (currentCoordinate[i, 0] != rowIndex)
                {
                    return false;
                }

                // 列相等
                if (currentCoordinate[i, 1] != columnIndex)
                {
                    return false;
                }

                // 层连续
                if (currentCoordinate[i, 2] != levelIndex++)
                {
                    return false;
                }
            }

            return true;
        }

        #endregion

        #region 通知观察者

        /// <summary>
        /// 通知观察者处理
        /// </summary>
        public override void notifyObserver()
        {
            if (observers == null || observers.Count == 0)
            {
                LoggerHelper.Info("无观察者");
                return;
            }

            foreach (var obs in observers)
            {
                obs.response(ObserverNotifyType.StackingProcess, this.notifyObj);
            }
        }

        /// <summary>
        /// 通知观察者告警
        /// </summary>
        public override void notifyWarningObserver()
        {
            if (observers == null || observers.Count == 0)
            {
                LoggerHelper.Info("无观察者或条码错误");
                return;
            }

            foreach (var obs in observers)
            {
                obs.warningResponse(ObserverWarningNotifyType.ScanBeforeInputWarning, this.notifyObj);   // 待修改
            }
        }

        #endregion
    }
}
